const fs = require("fs");
const path = require("path");
const tinify = require("tinify");
const os = require('os')
let cfg = require('./config.json');
let COMPRESS_PATH_LIST = cfg.compressPathList;
tinify.key = cfg.tinify_key;
let compressionsThisMonth = tinify.compressionCount;

/**
 * 分帧处理
 * @param item_arr item数组
 * @param process 处理函数
 * @param finish_cb 结束函数
 * @param time 每帧最大时限
 * @param frame_time 每帧停顿间隔
 * @returns 
 */
function frameProcess(item_arr, process, finish_cb, time = 1, frame_time = 500) {
    let idx = 0;
    function _frameProcess(item_arr, process, finish_cb, time = 1) {
        // console.log('frameProcess')
        let last_time = new Date().getTime()
        while (item_arr.length) {
            if (new Date().getTime() - last_time > 1) {
                setTimeout(() => {
                    _frameProcess(item_arr, process, finish_cb);
                }, frame_time)
                return
            }
            let item = item_arr.shift();
            process(item, idx)
            idx++;
        }
        if (finish_cb) finish_cb();
    }
    _frameProcess(item_arr, process, finish_cb, time)
}

function execCmd(cmd, cb) {
    let exec = require('child_process').exec;
    exec(cmd, function (error, stdout, stderr) {
        // 获取命令执行的输出
        // if (error) {
        //     Editor.log('error', error);
        // }
        // else if (stdout) {
        //     Editor.log('stdout', stdout);
        // }
        // else if (stderr) {
        //     Editor.log('stderr', stderr);
        // }
        if (cb) cb(error, stdout, stderr)
    })
}

class PngCompressor {
    static defaultOptions = {
        isCheckPng: false,
    }
    static compress_type = {
        pngquant: '0',
        tinypng: '1',
    }
    static system = {
        linux: 'Linux',
        mac: 'Darwin',
        window: 'Windows_NT',
    }

    totalFiles = [];
    successFiles = [];
    failFiles = [];
    totalBeforeSize = 0;
    totalNowSize = 0;
    compress_id = 0;
    /**压缩方式 */
    compressFunc(filePath, callback) { }
    buildCallback = function () { }
    constructor(cfg) {
        this.init(cfg)
    }
    init(cfg) {
        cfg = cfg || {
            compressPathList: [
                "assets/"
            ],
            compress_type: 0,
            isBuildFinshCompress: false,
            tinify_key: "asdad",
            min_quality: 65,
            max_quality: 80,
            compress_type: 0,
            max_log_count: 1024,
            tinify_key: '',
            ignore_compressed_png: false,
            is_to_bottom_log: true,
        }
        this.compress_id = 0;
        Editor.log('初始化数据..');
        if (cfg.compress_type == PngCompressor.compress_type.tinypng && !cfg.tinify_key) {
            Editor.log('缺少账户ID请先去 https://tinify.cn/developers 申请账号，然后再在config.js中配置')
            let p = __dirname + '/config.json';
            Editor.log('config路径:', p)
            execCmd(`code ${p}`)
            execCmd(`explorer https://tinify.cn/developers`)
            throw '';
        }
        tinify.key = cfg.tinify_key;
        Editor.log('初始化数据..1', JSON.stringify(cfg.compress_type));
        if (cfg.compress_type == PngCompressor.compress_type.tinypng) {
            this.compressFunc = this.tinypngCompress;
            Editor.log('tinypngCompress')
        } else if (cfg.compress_type == PngCompressor.compress_type.pngquant) {
            this.compressFunc = this.pngquantCompress;
            Editor.log('pngquantCompress')
        } else {
            Editor.log('no-pngquantCompress')
            this.compressFunc = this.pngquantCompress;
        }
        Editor.log('系统：', os.type())
        this.totalBeforeSize = 0;
        this.totalNowSize = 0;
        this.totalFiles = [];
        this.successFiles = [];
        this.failFiles = [];
        this.compressEffectPercent = 0;
    }

    /**
     * 获取db打头的路径
     **/
    getDBUrl(realFilePath) {
        let db_path = Editor.url('db://assets/..') + '\\';
        realFilePath = realFilePath.replace(db_path, '');
        realFilePath = realFilePath.replace('\\', '/');
        Editor.log('realFilePath3', 'db://' + realFilePath)
        return 'db://' + realFilePath;
    }
    /**
     * 压缩
     * @param {*} sFilePath 
     * @param {*} bIsSeq 串行压缩
     * @param {*} progress 过程回调
     * @param {*} callback 结束回调
     */
    compressAll(pathList, bIsSeq, progress, callback) {
        this.totalFiles = pathList.concat();
        let max = pathList.length;
        let finish_count = 0;
        if (!bIsSeq) {
            let jumpcount = 0;
            frameProcess(pathList, (img, idx) => {
                Editor.log('idx', idx)
                this.compress(img, (beforeSize, nowSize) => {
                    // let db_path = img.replace(Editor.url('db://assets/..') + '\\', '')
                    // Editor.log('db_path', `db://${db_path}`)
                    finish_count++;
                    if (callback && finish_count >= max) {
                        callback(this.compressEffectPercent)
                        return;
                    }

                    if (progress) {
                        progress(finish_count, max, img, beforeSize, nowSize);
                    } else {
                        Editor.log('jumpcount', ++jumpcount, '剩余:', pathList.length);
                    }
                    Editor.assetdb.refresh(this.getDBUrl(img));
                })
                // progress(idx, max, img);
            }, () => {
                // if (callback) callback(this.compressEffectPercent)
            }, 1, 500)
        } else {
            let compressLoop = () => {
                // Editor.log(`compressLoop `, pathList)
                if (pathList.length > 0) {
                    let img = pathList.shift();
                    // Editor.log(`img： ${img}`)
                    this.compress(img, () => {
                        compressLoop();
                        Editor.log('剩余:', pathList.length);
                        // Editor.log(`进度： ${path.basename(img)},success: ${this.successFiles.length}, fail: ${this.failFiles.length}, total: ${this.totalFiles.length}`)
                    })
                } else {
                    Editor.log(`完成进度： ${this.getPercent()}%`)
                }
            }
            compressLoop()
        }
    }
    getPercent() {
        if (this.totalFiles.length == 0) return 100;
        return (this.successFiles.length + this.failFiles.length) / this.totalFiles.length * 100;
    }
    /**获取配置列表中所有的图片路径 */
    getConfigAllImgPathList(list, callback) {
        let count = 0;
        for (let curPath of list) {
            let dirPath = Editor.url("db://" + curPath);
            this.getImagePathListRecursive(dirPath, false, (pathList) => {
                pathList = pathList.concat(pathList);
                count++
                Editor.log('getConfigAllImgPathList count', count)
                if (count == list.length) {
                    callback(pathList)
                }
            })
        }
    }
    /**获取配置列表中未压缩的图片路径 */
    getConfigUncompressedImgPathList(list, callback) {
        let count = 0;
        for (let curPath of list) {
            let dirPath = Editor.url("db://" + curPath);
            this.getImagePathListRecursive(dirPath, true, (pathList) => {
                pathList = pathList.concat(pathList);
                count++
                Editor.log('getConfigUncompressedImgPathList count', count)
                if (count == list.length) {
                    callback(pathList)
                }
            })
        }
    }

    /**获取单个路径的图片列表 */
    getImagePathListRecursive(searchPath, isCheckCompressState, callback) {
        // Editor.log('getImagePathListRecursive', searchPath)

        let pathList = [];
        if (fs.existsSync(searchPath)) {
            // Editor.log('getImagePathListRecursive ===>0', searchPath)
            fs.stat(searchPath, (e, stat) => {
                if (e) Editor.error(e);
                if (stat.isFile() && (!isCheckCompressState || !this.isCompressed(searchPath)) && searchPath.match(/.png$/)) {
                    // 文件就直接返回
                    // Editor.log(isCheckCompressState, !isCompressed(searchPath))
                    // Editor.log('getImagePathListRecursive ===>1', searchPath)
                    pathList.push(searchPath);
                    if (callback) callback(pathList)
                } else if (stat.isDirectory()) {
                    // 文件夹就再往下找
                    fs.readdir(searchPath, (e, dirItems) => {
                        if (e) Editor.error(e)
                        let count = 0;
                        // Editor.log('getImagePathListRecursive ===>2', searchPath)
                        if (dirItems.length == 0) {
                            if (callback) callback(pathList);
                        } else {
                            dirItems.forEach(v => {
                                let newSearchPath = `${searchPath}/${v}`;
                                this.getImagePathListRecursive(newSearchPath, isCheckCompressState, (imgPathList) => {
                                    ++count;
                                    if (imgPathList && imgPathList.length) {
                                        pathList = pathList.concat(imgPathList);
                                    }
                                    // Editor.log('getImagePathListRecursive ===>3', searchPath, count, dirItems.length, pathList)
                                    if (count == dirItems.length) {
                                        // Editor.log('getImagePathListRecursive ===>4', searchPath, pathList)
                                        if (callback) callback(pathList)
                                    }
                                })
                            })
                        }
                    })
                } else {
                    if (callback) callback(pathList)
                }
            })
        } else {
            Editor.log('getImagePathListRecursive ===>5', searchPath)
            if (callback) callback(pathList)
        }
    }
    /**是否压缩过 */
    isCompressed(filePath) {
        let buf = fs.readFileSync(filePath)
        let bitsPerPixel = buf[24] & 0xff;
        if (buf[25] == 2) {
            bitsPerPixel *= 3;
        } else if (buf[25] == 6) {
            bitsPerPixel *= 4;
        }
        return bitsPerPixel < 24;
    }
    /**是否是png */
    isPng(filePath) {
        return filePath.match(/.png$/);
    }
    /**压缩函数 */
    compress(filePath, callback) {
        let compressPath = filePath;

        // Editor.log('compress', path.basename(compressPath))
        if (compressPath.match(/.meta$/)) {
            if (callback) callback()
            return
        }
        if (!this.isPng(compressPath)) {
            if (callback) callback()
            // Editor.log('不是png文件，略过', path.basename(compressPath))
            return;
        }
        let islegalPath = true;
        if (compressPath.indexOf(' ') != -1
            || compressPath.match(/[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/g)
        ) {
            islegalPath = false;
            Editor.log('非法路径，拷贝临时目录压缩', compressPath)
            let tempDir = path.join(__dirname, 'temp');
            if (!fs.existsSync(tempDir)) {
                fs.mkdirSync(tempDir);
            }
            let dst = path.join(tempDir, `temp_${++this.compress_id}.png`);
            if (fs.existsSync(dst)) {
                fs.unlinkSync(dst);
            }
            fs.copyFileSync(compressPath, dst)
            compressPath = dst;
            // Editor.log('不合法路径 切换', compressPath)
        }

        fs.stat(compressPath, (e, stat) => {
            if (e) Editor.error(e)
            this.totalBeforeSize += stat.size;
            // Editor.log('compress ===> 1', compressPath)
            this.compressFunc(compressPath, (error) => {
                // Editor.log('compress ===> 2', compressPath)
                let nowSize = fs.statSync(compressPath).size;
                this.totalNowSize += nowSize;
                if (error) {
                    // Editor.log('compress ===> 3', compressPath)
                    this.failFiles.push(compressPath);
                } else {
                    // Editor.log('compress ===> 4', compressPath)
                    this.successFiles.push(compressPath);
                }
                let done = () => {
                    if (callback) callback(stat.size, nowSize);
                    // Editor.log('compress ===> 8', compressPath)
                    // Editor.log(this.totalFiles.length, this.successFiles.length, this.failFiles.length)
                    if (this.totalFiles.length == (this.successFiles.length + this.failFiles.length)) {
                        // Editor.log('compress ===> 9', compressPath)
                        this.finishLog();
                    }
                }
                // Editor.log('compress ===> 5', compressPath)
                if (compressPath != filePath && fs.existsSync(compressPath) && fs.existsSync(filePath)) {
                    // Editor.log('compress ===> 6', compressPath)
                    Editor.log('从临时文件中取回', compressPath, filePath)
                    fs.unlink(filePath, (e) => {
                        if (e) Editor.error(e)
                        fs.copyFile(compressPath, filePath, (e) => {
                            if (e) Editor.error(e)
                            fs.unlink(compressPath, (e) => {
                                if (e) Editor.error(e)
                                done()
                            })
                        })
                    })
                } else {
                    // Editor.log('compress ===> 7', compressPath)
                    done()
                }
            })
        });
    }
    /**tinypng压缩 */
    tinypngCompress(filePath, callback) {
        const source = tinify.fromFile(filePath);
        source.toFile(filePath, function (err) {
            if (err) {
                if (err instanceof tinify.AccountError) {
                    Editor.error("当前账户请求已达到限制");
                } else if (err instanceof tinify.ClientError) {
                    Editor.error("文件数据异常，请检查是否是图片文件");
                } else if (err instanceof tinify.ServerError) {
                    Editor.error("服务器原因暂不可用，请稍后再试");
                } else if (err instanceof tinify.ConnectionError) {
                    Editor.error("连接失败，请检查网络...");
                } else { }
            }
            if (callback) callback(err)
        });
    }
    /**pngquant压缩 */
    pngquantCompress(filePath, callback) {
        let cmd;
        // 参数文档： https://pngquant.org/
        let quality = '65-80'; // 图像质量
        if (os.type() == PngCompressor.system.window) {
            cmd = `${__dirname}/pngquant-win.exe --force --verbose 256 ${filePath} --output ${filePath}`;
        } else {
            // 修改文件权限
            execCmd(`chmod a+x ./${__dirname}/pngquant-mac`);
            cmd = `./${__dirname}/pngquant-mac --force --verbose 256 ${filePath} --output ${filePath}`;
        }

        execCmd(cmd, (err) => {
            if (callback) callback(err)
        })
    }
    /**结束log */
    finishLog() {
        Editor.log('/**********压缩全部完成******************/');
        Editor.log('压缩成功文件', this.successFiles.length, '个', this.successFiles);
        Editor.log('压缩失败文件', this.failFiles.length, '个', this.failFiles);
        Editor.log('共', this.totalFiles.length, '个');
        // Editor.log('无应答', this.totalFiles.length - (this.successFiles.length + this.failFiles.length), '个');
        // Editor.log("已请求次数", compressionsThisMonth);
        this.compressEffectPercent = (this.totalBeforeSize - this.totalNowSize) / this.totalBeforeSize * 100;
        Editor.log(`总体压缩效果： -${this.compressEffectPercent}%`)
        this.buildCallback();
    }

    buildCompress(dirPath, callback) {
        Editor.log('构建完成，开始压缩图片，请稍等..');
        this.buildCallback = callback;
        let pathList = this.getImagePathListRecursive(dirPath, true)
        if (pathList.length > 0) {
            for (let img of pathList) {
                Editor.log(path.basename(img));
                this.compress(img);
            }
        } else {
            Editor.log('没有需要压缩的图片。')
            this.buildCallback();
        }
    }

}

module.exports = PngCompressor;